package com.sl.transport.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.itheima.em.sdk.EagleMapTemplate;
import com.itheima.em.sdk.enums.ProviderEnum;
import com.itheima.em.sdk.vo.Coordinate;
import com.sl.transport.common.exception.SLException;
import com.sl.transport.common.util.PageResponse;
import com.sl.transport.domain.DispatchConfigurationDTO;
import com.sl.transport.domain.OrganDTO;
import com.sl.transport.domain.TransportLineNodeDTO;
import com.sl.transport.domain.TransportLineSearchDTO;
import com.sl.transport.entity.line.TransportLine;
import com.sl.transport.entity.node.AgencyEntity;
import com.sl.transport.entity.node.BaseEntity;
import com.sl.transport.entity.node.OLTEntity;
import com.sl.transport.entity.node.TLTEntity;
import com.sl.transport.enums.DispatchMethodEnum;
import com.sl.transport.enums.ExceptionEnum;
import com.sl.transport.enums.TransportLineEnum;
import com.sl.transport.repository.TransportLineRepository;
import com.sl.transport.service.CostConfigurationService;
import com.sl.transport.service.DispatchConfigurationService;
import com.sl.transport.service.OrganService;
import com.sl.transport.service.TransportLineService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;

/**
 * 这个不能实现ServiceImpl因为他的entity没有实现baseEntity
 * 他不是通用的，需要自己单独去写方法
 */

@Service
public class TransportLineServiceImpl implements TransportLineService {

    @Resource
    private TransportLineRepository transportLineRepository;

    @Resource
    private OrganService organService;

    @Resource
    private EagleMapTemplate eagleMapTemplate;

    @Resource
    private CostConfigurationService costConfigurationService;

    @Resource
    private DispatchConfigurationService dispatchConfigurationService;

    /**
     * 新增路线  TODO 自己写的
     *
     * @param transportLine 路线数据
     * @return 是否成功
     */
    @Override
    public Boolean createLine(TransportLine transportLine) {
        // 1.根据type获取枚举类型   线路类型：干线  支线  接驳路线
        TransportLineEnum transportLineEnum = TransportLineEnum.codeOf(transportLine.getType());
        if (ObjectUtil.isEmpty(transportLineEnum)) {
            throw new SLException(ExceptionEnum.TRANSPORT_LINE_TYPE_ERROR);
        }

        // 2.看起点和终点是否相同的结点
        if (ObjectUtil.equal(transportLine.getStartOrganId(), transportLine.getEndOrganId())) {
            throw new SLException(ExceptionEnum.START_END_ORGAN_SAME);
        }

        // 3.设置起点和终点机构
        BaseEntity firstNode;
        BaseEntity secondNode;

        switch (transportLineEnum) {
            case TRUNK_LINE:
                firstNode = OLTEntity.builder().bid(transportLine.getStartOrganId()).build();
                secondNode = OLTEntity.builder().bid(transportLine.getEndOrganId()).build();
                break;
            case BRANCH_LINE:
                firstNode = TLTEntity.builder().bid(transportLine.getStartOrganId()).build();
                secondNode = OLTEntity.builder().bid(transportLine.getEndOrganId()).build();
                break;
            case CONNECT_LINE:
                firstNode = AgencyEntity.builder().bid(transportLine.getStartOrganId()).build();
                secondNode = TLTEntity.builder().bid(transportLine.getEndOrganId()).build();
                break;
            default:
                throw new SLException(ExceptionEnum.TRANSPORT_LINE_TYPE_ERROR);
        }

        // 4.判断路线是否重复
        Long count = transportLineRepository.queryCount(firstNode, secondNode);

        if (count > 0) {
            throw new SLException(ExceptionEnum.TRANSPORT_LINE_ALREADY_EXISTS);
        }

        // 相当于是新建的因此id设置为null
        transportLine.setId(null);
        transportLine.setCreated(System.currentTimeMillis());
        transportLine.setUpdated(transportLine.getCreated());

        // 5.根据高德地图获取两个路线的之间关系  花费 时间 预计结束时间等
        //TODO 补充信息
        this.infoFromMap(firstNode, secondNode, transportLine);


        // 这里就是创建的两条路线  往返的路线
        count = this.transportLineRepository.create(firstNode, secondNode, transportLine);
        return count > 0;
    }

    /**
     * 通过地图查询距离、时间，计算成本 TODO 补充路线的其他信息
     *
     * @param firstNode
     * @param secondNode
     * @param transportLine
     */
    private void infoFromMap(BaseEntity firstNode, BaseEntity secondNode, TransportLine transportLine) {

        // 调用eagleMap里面的api来获取到两个地点之间的路线信息

        // 1.查询节点数据
        OrganDTO startOrgan = this.organService.findByBid(firstNode.getBid());
        if (ObjectUtil.hasEmpty(startOrgan, startOrgan.getLongitude(), startOrgan.getLatitude())) {
            throw new SLException("请先完善机构信息");
        }
        OrganDTO endOrgan = this.organService.findByBid(secondNode.getBid());
        if (ObjectUtil.hasEmpty(endOrgan, endOrgan.getLongitude(), endOrgan.getLatitude())) {
            throw new SLException("请先完善机构信息");
        }

        // 2.查询地图服务商
        Coordinate origin = new Coordinate(startOrgan.getLongitude(), startOrgan.getLatitude());
        Coordinate destination = new Coordinate(endOrgan.getLongitude(), endOrgan.getLatitude());

        // 3.设置高德地图参数，默认是不返回预计耗时的，需要额外设置参数
        Map<String, Object> param = MapUtil.<String, Object>builder().put("show_fields", "cost").build();

        // 第一个参数表示高德地图  origin初始地址   destination目的地址  param显示预计耗时
        String driving = this.eagleMapTemplate.opsForDirection().driving(ProviderEnum.AMAP, origin, destination, param);
        if (StrUtil.isEmpty(driving)) {
            return;
        }
        // 自己打断点来看这个json字符串，你才知道下面的取值为什么是 route.paths[0].cost.duration

        // 4.序列化高德地图的给我们的返回值
        JSONObject jsonObject = JSONUtil.parseObj(driving);

        // 解析  耗时时间
        // 时间，单位：秒
        // route.paths[0].cost.duration这个解析是通过自己debug然后看里面的json数据的格式才知道是这样写的
        // 只是这样写，更加的简便 不用get("").get("")  这样用JSONObject就可以直接getPath("")
        Long duration = Convert.toLong(jsonObject.getByPath("route.paths[0].cost.duration"), -1L);
        transportLine.setTime(duration);
        //距离，单位：米
        // 第二个参数就是如果Convert.toDouble没有成功就用默认值
        Double distance = Convert.toDouble(jsonObject.getByPath("route.paths[0].distance"), -1d);
        transportLine.setDistance(distance);
        // 解析 路线距离

        // 解析路线成本：  路线距离/1000 * 成本
        // 这个是写好了的  根据不同的路线获取成本
        Double cost = costConfigurationService.findCostByType(transportLine.getType());
        transportLine.setCost(cost);

    }

    /**
     * 修改路线
     *
     * @param transportLine 路线数据
     * @return 是否成功
     */
    @Override
    public Boolean updateLine(TransportLine transportLine) {
        // 1、先查后改
        // 判断是否存在
        TransportLine transportLineData = this.queryById(transportLine.getId());
        if (null == transportLineData) {
            throw new SLException(ExceptionEnum.TRANSPORT_LINE_NOT_FOUND);
        }

        // 2、拷贝数据，忽略null值以及不能修改的字段
        BeanUtil.copyProperties(transportLine, transportLineData, CopyOptions.create().setIgnoreNullValue(true).setIgnoreProperties("type", "startOrganId", "startOrganName", "endOrganId", "endOrganName"));

        transportLineData.setUpdated(System.currentTimeMillis());

        // 3、调用修改
        Long count = this.transportLineRepository.update(transportLineData);
        return count > 0;
    }

    /**
     * 删除路线
     *
     * @param id 路线id
     * @return 是否成功
     */
    @Override
    public Boolean deleteLine(Long id) {
        // 1.调用删除路线方法
        Long count = this.transportLineRepository.remove(id);
        return count > 0;
    }

    /**
     * 分页查询路线   并返回路线列表
     *
     * @param transportLineSearchDTO 搜索参数
     * @return 路线列表
     */
    @Override
    public PageResponse<TransportLine> queryPageList(TransportLineSearchDTO transportLineSearchDTO) {
        // 分页查询
        return transportLineRepository.queryPageList(transportLineSearchDTO);
    }

    /**
     * 查询两个网点之间最短的路线，最大查询深度为：10
     *
     * @param startId 开始网点id
     * @param endId   结束网点id
     * @return 路线
     */
    @Override
    public TransportLineNodeDTO queryShortestPath(Long startId, Long endId) {
        AgencyEntity start = AgencyEntity.builder().bid(startId).build();
        AgencyEntity end = AgencyEntity.builder().bid(endId).build();
        if (ObjectUtil.hasEmpty(start, end)) {
            throw new SLException(ExceptionEnum.START_END_ORGAN_NOT_FOUND);
        }
        return transportLineRepository.findShortestPath(start, end);
    }

    /**
     * 找到最低消费路线
     *
     * @param startId 开始网点id
     * @param endId   结束网点id
     * @return 路线集合
     */
    @Override
    public TransportLineNodeDTO findLowestPath(Long startId, Long endId) {
        AgencyEntity start = AgencyEntity.builder().bid(startId).build();
        AgencyEntity end = AgencyEntity.builder().bid(endId).build();

        if (ObjectUtil.hasEmpty(start, end)) {
            throw new SLException(ExceptionEnum.START_END_ORGAN_NOT_FOUND);
        }

        // 最后一个就表示为1个
        List<TransportLineNodeDTO> pathList = this.transportLineRepository.findPathList(start, end, 10, 1);
        if (CollUtil.isNotEmpty(pathList)) {
            return pathList.get(0);
        }
        return null;
    }

    /**
     * 根据调度策略查询路线
     *
     * @param startId 开始网点id
     * @param endId   结束网点id
     * @return 路线
     */
    @Override
    public TransportLineNodeDTO queryPathByDispatchMethod(Long startId, Long endId) {
        //调度方式配置
        DispatchConfigurationDTO configuration = this.dispatchConfigurationService.findConfiguration();
        int method = configuration.getDispatchMethod();

        //默认是采用的成本最低的  数据库里面存的就是2
        //调度方式，1转运次数最少，2成本最低
        if (ObjectUtil.equal(DispatchMethodEnum.SHORTEST_PATH.getCode(), method)) {
            return this.queryShortestPath(startId, endId);
        } else {
            return this.findLowestPath(startId, endId);
        }
    }

    /**
     * 根据ids批量查询路线
     *
     * @param ids id列表
     * @return 路线数据
     */
    @Override
    public List<TransportLine> queryByIds(Long... ids) {
        // 批量查询  就是用in就可以
        return transportLineRepository.queryByIds(ids);
    }

    /**
     * 根据id查询路线
     *
     * @param id 路线id
     * @return 路线数据
     */
    @Override
    public TransportLine queryById(Long id) {
        // 这个就直接使用 n.bid = id就可以实现了
        return this.transportLineRepository.queryById(id);
    }
}
